package com.wujunshen.swagger;

import com.wujunshen.swagger.annotation.ApiVersion;
import com.wujunshen.swagger.properties.SwaggerProperties;
import io.swagger.annotations.ApiOperation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.AbstractRefreshableApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.lang.NonNull;
import springfox.documentation.PathProvider;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.paths.AbstractPathProvider;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @author frank woo(吴峻申) <br>
 * email:<a href="mailto:frank_wjs@hotmail.com">frank_wjs@hotmail.com</a> <br>
 * @date 2020/2/7 5:33 下午 <br>
 */
@Slf4j
@Configuration
@EnableSwagger2
@EnableConfigurationProperties(SwaggerProperties.class)
@ConditionalOnProperty(name = "swagger.enable", havingValue = "true")
public class SwaggerAutoConfiguration implements ApplicationContextAware {
    @Resource
    private SwaggerProperties swaggerProperties;
    
    /**
     * Bean factory for this context
     */
    private ConfigurableListableBeanFactory beanFactory;
    
    /**
     * 应用上下文设定
     *
     * @param applicationContext ApplicationContext
     * @throws BeansException BeansException
     */
    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext)
            throws BeansException {
        if (applicationContext instanceof AbstractRefreshableApplicationContext) {
            beanFactory = ((AbstractRefreshableApplicationContext) applicationContext).getBeanFactory();
        } else {
            beanFactory = ((GenericApplicationContext) applicationContext).getBeanFactory();
        }
    }
    
    /**
     * 根据配置文件中版本号集合，动态生成多个Docket类
     */
    @PostConstruct
    public void init() {
        log.info(
                "\ntitle is:{}\ndescription is:{}\nversions is:{}\n",
                swaggerProperties.getTitle(),
                swaggerProperties.getDescription(),
                swaggerProperties.getVersions());
        List<String> beanNameList = new ArrayList<>();
        // 去重
        Set<String> versions =
                Arrays.stream(swaggerProperties.getVersions()).collect(Collectors.toSet());
        for (String versionNo : versions) {
            beanFactory.registerSingleton(versionNo + "Docket", initDocket(versionNo));
            beanNameList.add(versionNo + "Docket");
        }
        log.info(
                "\n{} initialization completed\n", beanNameList.isEmpty() ? "" : beanNameList.toString());
    }
    
    /**
     * 初始化Docket类,根据版本号设定
     *
     * @param versionNo 版本号
     * @return Docket
     */
    private Docket initDocket(String versionNo) {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(initApiInfo(versionNo))
                .pathProvider(apiPathProvider())
                .groupName(versionNo)
                .select()
                .paths(PathSelectors.any())
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                .apis(input -> isGroup(input, versionNo))
                .build();
    }
    
    /**
     * 判断是否标配配置文件中的版本号设定
     *
     * @param input     RequestHandler
     * @param versionNo 版本号
     * @return 是否属于该组, 组名即为版本号
     */
    private boolean isGroup(RequestHandler input, String versionNo) {
        ApiVersion apiVersion =
                Objects.requireNonNull(input).getHandlerMethod().getMethodAnnotation(ApiVersion.class);
        
        if (apiVersion == null) {
            return false;
        }
        
        return Arrays.asList(apiVersion.version()).contains(versionNo);
    }
    
    /**
     * 将来可以扩展,可以自定义上下文路径
     *
     * @return PathProvider
     */
    private PathProvider apiPathProvider() {
        return new AbstractPathProvider() {
            @Override
            protected String applicationPath() {
                return swaggerProperties.getContextPath();
            }
            
            @Override
            protected String getDocumentationPath() {
                return "";
            }
        };
    }
    
    /**
     * 构建api文档的详细信息
     *
     * @param versionNo 版本号
     * @return ApiInfo
     */
    private ApiInfo initApiInfo(String versionNo) {
        return new ApiInfoBuilder()
                .title(swaggerProperties.getTitle())
                .description(swaggerProperties.getDescription())
                .version(versionNo)
                .termsOfServiceUrl("")
                .contact(
                        new Contact(
                                swaggerProperties.getContactName(),
                                swaggerProperties.getContactUrl(),
                                swaggerProperties.getContactEmail()))
                .license(swaggerProperties.getLicense())
                .licenseUrl(swaggerProperties.getLicenseUrl())
                .extensions(new ArrayList<>())
                .build();
    }
}
